#include "output.hpp"

namespace my_log
{
    void screen_output::landing(std::string_view input, unsigned int level, flusher& f, output_t* call_back)
    {
        if (f.isImmediate(level))
        {
            std::cout << input << std::endl;
        }
        else
        {
            std::cout << input << '\n';
            f.update(call_back, level);
        }
    }

    void screen_output::destroy()
    {
        delete this;
    }

    void screen_output::flush()
    {
        std::cout.flush();
    }

    file_output::file_output(const std::filesystem::path& name, size_t buf_size)
        : _fout(name, std::ios::out)
        , _buf(buf_size, '\0')
    {
        _fout.rdbuf()->pubsetbuf(_buf.data(), _buf.size());
    }

    file_output::~file_output()
    {
        _fout.close();
    }

    file_output::file_output(file_output&& other) noexcept
        : _fout(std::move(other._fout))
        , _buf(std::move(other._buf))
    {
        other._fout = std::ofstream{};
    }

    void file_output::landing(std::string_view input, unsigned int level, flusher& f, output_t* call_back)
    {
        if (f.isImmediate(level))
        {
            _fout << input << std::endl;
        }
        else
        {
            _fout << input << '\n';
            f.update(call_back, level);
        }
    }

    void file_output::destory()
    {
        delete this;
    }

    void file_output::flush()
    {
        _fout.flush();
    }

    roll_file_output::roll_file_output(const std::filesystem::path& name_base, size_t single_file_size, size_t buf_size)
        : _name_base(name_base)
        , _fout(nameGenerator(), std::ios::out)
        , _written_byte(0)
        , _max_single_file_size(defaultSingleFileSize())
        , _buf(buf_size, '\0')
    {
        _fout.rdbuf()->pubsetbuf(_buf.data(), _buf.size());
        setMaxSingleFileSize(single_file_size);
    }

    roll_file_output::~roll_file_output()
    {
        _fout.close();
    }

    roll_file_output::roll_file_output(roll_file_output&& other) noexcept
        : _name_base(std::move(other._name_base))
        , _fout(std::move(other._fout))
        , _written_byte(other._written_byte.load())
        , _max_single_file_size(other._max_single_file_size.load())
        , _ctrl()
        , _buf(std::move(other._buf))
    {
        other._fout = std::ofstream{};
    }

    const std::atomic<size_t>& roll_file_output::getMaxSingleFileSize() noexcept
    {
        return _max_single_file_size;
    }

    void roll_file_output::setMaxSingleFileSize(size_t num) noexcept
    {
        if (num >= MinSingleFileSize())
        {
            _max_single_file_size.store(num);
        }
    }

    std::filesystem::path roll_file_output::nameGenerator() const noexcept
    {
        std::filesystem::path name = _name_base;
        name.append(std::format(" {0:%T}", std::chrono::system_clock::now()));
        return name;
    }

    constexpr size_t roll_file_output::defaultSingleFileSize() noexcept
    {
        return static_cast<size_t>(1024) * 1024 * 10;
    }

    constexpr size_t roll_file_output::MinSingleFileSize() noexcept
    {
        return static_cast<size_t>(1024) * 16;
    }

    void roll_file_output::landing(std::string_view input, unsigned int level, flusher& f, output_t* call_back)
    {
        if (_written_byte.load() + input.size() > _max_single_file_size.load())
        {
            std::lock_guard lock(_ctrl);
            if (_written_byte.load() + input.size() > _max_single_file_size.load())
            {
                _fout = std::ofstream { nameGenerator(), std::ios::out };
                _written_byte.store(0);
            }
        }
        if (f.isImmediate(level))
        {
            _fout << input << std::endl;
        }
        else
        {
            _fout << input << '\n';
            f.update(call_back, level);
        }
        _written_byte.fetch_add(input.size());
    }

    void roll_file_output::destory()
    {
        delete this;
    }

    void roll_file_output::flush()
    {
        _fout.flush();
    }

    flusher::timer::timer(output_t* target_ptr)
        : _begin(std::chrono::steady_clock::now())
        , _duration(dura{ 0 })
        , _call_back_ptr(target_ptr)
        , _level(-1)
    {}

    bool flusher::timer::empty() noexcept
    {
        return _level == -1;
    }

    int flusher::timer::timeLeft(dura& duration) const
    {
        if (_level == -1)
        {
            duration = dura{ 0 };
            return -1;
        }
        dura time_passed = std::chrono::duration_cast<dura>(std::chrono::steady_clock::now() - _begin);
        if (_duration <= time_passed)
        {
            duration = dura{ 0 };
            return 1;
        }
        else
        {
            duration = _duration - time_passed;
            return 0;
        }
    }

    void flusher::timer::timeout()
    {
        std::visit(flush_visitor{}, *_call_back_ptr);
        _level = -1;
    }

    void flusher::timer::reset(unsigned int level, dura duration)
    {
        _begin = std::chrono::steady_clock::now();
        _duration = duration;
        _level = level;
    }

    flusher::flusher(const std::atomic<size_t>& status, const std::array<dura, 5>& level_times)
        : _timer_update_queue()
        , _timer_table()
        , _level_times(level_times)
        , _status(status)
        , _is_flush_time_same(true)
        , _th()
        , _cond()
        , _ctrl()
    {
        dura tem = _level_times[0];
        for (unsigned int i = 1; i < _level_times.size(); ++i)
        {
            if (tem != _level_times[i])
            {
                _is_flush_time_same = false;
            }
        }
        std::function<void()> call_back = std::bind(&flusher::entry, this);
        _th = std::thread{ call_back };
    }

    flusher::~flusher()
    {
        _cond.notify_all();
        _th.join();
    }

    bool flusher::needUpdate(output_t* target_ptr, int level) const
    {
        if (_is_flush_time_same)
        {
            return _timer_table.at(target_ptr)._level == -1;
        }
        else
        {
            return level > _timer_table.at(target_ptr)._level;
        }
    }

    bool flusher::isImmediate(int level) const
    {
        return _level_times.at(level) == dura{ 0 };
    }

    void flusher::update(output_t* target_ptr, int level)
    {
        if (needUpdate(target_ptr, level))
        {
            _timer_update_queue.push(std::make_pair(target_ptr, level));
            _cond.notify_one();
        }
    }

    void flusher::addTimer(output_t* target_ptr)
    {
        if (_timer_table.find(target_ptr) != _timer_table.end())
        {
            throw log_error{ "flusher::addTimer: timer has existed" };
        }
        _timer_table.emplace(target_ptr, timer{ target_ptr });
    }

    void flusher::entry()
    {
        std::unique_lock lock(_ctrl, std::try_to_lock);
        std::pair<output_t*, int> new_time;
        bool no_timer = false, need_flush = false;
        dura left_time, shortest_duration, new_duration;
        timer* target_timer_ptr = nullptr;
        _cond.wait(lock, [this](){ return _status.load(std::memory_order::relaxed) != 0; });
        while (_status.load(std::memory_order::relaxed) != 2)
        {
            while (!_timer_update_queue.empty())
            {
                _timer_update_queue.pop(new_time);
                new_duration = _level_times.at(new_time.second);
                timer& ti = _timer_table.at(new_time.first);
                if (ti.timeLeft(left_time) != -1)
                {
                    if (new_duration < left_time)
                    {
                        ti.reset(new_time.second, new_duration);
                    }
                }
                else
                {
                    ti.reset(new_time.second, new_duration);
                }
            }
            no_timer = true;
            shortest_duration = dura{ 1000000 };
            for (auto it = _timer_table.begin(); _timer_update_queue.empty() && it != _timer_table.end(); ++it)
            {
                if (!it->second.empty())
                {
                    no_timer = false;
                    it->second.timeLeft(left_time);
                    if (left_time < shortest_duration)
                    {
                        shortest_duration = left_time;
                        target_timer_ptr = &it->second;
                    }
                }
            }
            if (_timer_update_queue.empty() && !no_timer)
            {
                need_flush = true;
                while (target_timer_ptr->timeLeft(left_time) != 1)
                {
                    if (left_time > _limition)
                    {
                        if (_timer_update_queue.empty())
                        {
                            _cond.wait_for(lock, left_time - _limition);
                            target_timer_ptr->timeLeft(left_time);
                            if (left_time > _limition && !_timer_update_queue.empty())
                            {
                                need_flush = false;
                                break;
                            }
                        }
                    }
                    else
                    {
                        std::this_thread::yield();
                    }
                }
                if (need_flush)
                {
                    target_timer_ptr->timeout();
                }
            }
            else if (no_timer)
            {
                _cond.wait(lock, [this](){ return _status.load(std::memory_order::relaxed) == 2 || !_timer_update_queue.empty(); });
            }
        }
    }
}